﻿using System;
using System.Collections.Generic;
using System.IO;
using LightInject;
using Mnk.Merger.Code.Commands;
using Mnk.Merger.Code.Common;
using Mnk.Merger.Code.Repository;
using Mnk.Merger.Code.Settings;

namespace Mnk.Merger.Code.UI
{
    class Application : IDisposable
    {
        private readonly ServicesRegistrar registrar = new ServicesRegistrar();
        private readonly IServiceContainer container;
        private const string DefaultConfigName = "merge-config.json";

        public Application()
        {
            container = registrar.Register();
        }

        public void Run(string[] arguments)
        {
            Console.WriteLine("Merger 1.03, by mnk92, 2015. web-site:https://merger.codeplex.com/");
            var command = "merge";
            if (arguments.Length >= 1) command = arguments[0].ToLower();
            switch (command)
            {
                case "merge":
                    if (DoMerge(arguments)) return;
                    break;
                case "bulk-merge":
                    if(DoBulkMerge(arguments))return;
                    break;
                case "bulk-switch":
                    if(DoBulkSwitch(arguments))return;
                    break;
                case "spy":
                    if(DoSpy(arguments))return;
                    break;
                case "update-cache":
                    if(DoUpdateCache(arguments))return;
                    break;
            }
            PrintHelp();
        }

        private bool DoUpdateCache(IList<string> arguments)
        {
            Console.WriteLine("Update cache selected");
            var pathToConfig = DefaultConfigName;
            if (arguments.Count > 1) pathToConfig = arguments[1];
            RegisterConfig(pathToConfig);
            var cache = container.GetInstance<IRepositoryCache>();
            Helpers.Handle(() => cache.Update());
            return true;
        }

        private bool DoSpy(IList<string> arguments)
        {
            Console.WriteLine("Collect info selected");
            if (arguments.Count != 4) return false;
            RegisterConfig(DefaultConfigName);
            var sourceUrl = FixUrl(arguments[1]);
            var targetUrl = FixUrl(arguments[2]);
            var revisionFrom = arguments[3];
            var ui = container.GetInstance<ISpyUi>();
            Helpers.Handle(() => ui.Collect(sourceUrl, targetUrl, revisionFrom));
            return true;
        }

        private bool DoBulkSwitch(IList<string> arguments)
        {
            Console.WriteLine("Bulk Switch selected");
            if (arguments.Count > 3) return false;
            var pathToConfig = DefaultConfigName;
            if (arguments.Count > 2) pathToConfig = arguments[2];
            var config = RegisterConfig(pathToConfig);
            string targetSourceUrl = config.MergeUrl;
            if (arguments.Count > 1)
            {
                targetSourceUrl= arguments[1];
            }
            var switcher = container.GetInstance<IBulkOperations>();
            Helpers.Handle(() => switcher.BulkSwitch(targetSourceUrl));
            return true;
        }

        private bool DoBulkMerge(IList<string> arguments)
        {
            Console.WriteLine("Bulk Merge selected");
            if (arguments.Count > 4) return false;
            var validation = false;
            if (arguments.Count > 1) validation = string.Equals(arguments[0], "A");
            var pathToConfig = DefaultConfigName;
            if (arguments.Count > 2)pathToConfig = arguments[1];
            RegisterConfig(pathToConfig);
            var merger = container.GetInstance<IBulkOperations>();
            Helpers.Handle(() => merger.BulkMerge(validation));
            return true;
        }

        private bool DoMerge(IList<string> arguments)
        {
            Console.WriteLine("Merge selected");
            if (arguments.Count > 4) return false;
            var pathToConfig = DefaultConfigName;
            if (arguments.Count > 2) pathToConfig = arguments[2];
            var config = RegisterConfig(pathToConfig);
            var projectName = config.ProjectName;
            if (arguments.Count > 1) projectName = arguments[1];
            var merger = container.GetInstance<IMergeUi>();
            if (arguments.Count == 4 && string.Equals(arguments[3].ToLower(), "a"))
            {
                merger.AutoMerge(projectName, string.Equals(arguments[3], "A"));
            }
            else
            {
                merger.Handle(projectName);
            }
            return true;
        }

        private MergeConfig RegisterConfig(string pathToConfig)
        {
            var s = container.GetInstance<IJsonDataSerializer>();
            var config = s.Load(pathToConfig, new MergeConfig());
            config.CacheFolder = FixPath(config.CacheFolder);
            config.SourcesFolder = FixPath(config.SourcesFolder);
            container.RegisterInstance(config);
            return config;
        }

        private static string FixPath(string path)
        {
            return Path.GetFullPath(Environment.ExpandEnvironmentVariables(path));
        }

        private static string FixUrl(string url)
        {
            while (url.Length > 0 && url.EndsWith("/")) url = url.Remove(url.Length - 1);
            return url;
        }


        private static void PrintHelp()
        {
            Console.WriteLine(@"
You are able to do next tasks (by default task = ""merge""):
1) Merge code - you will be able to work with ui via console, or do it in auto mode, if you will specify 'a' or 'A' (the last one with validation), as the last argument.
merger.exe merge [projectName] [pathToConfig] [a]
where: 
   projectName - name of the project to do merge - name of the folder, should be equal to the repository folder name. (if empty it will be readed from config file)
   pathToConfig - path to the merge config (by default ""merge-config.json"").

2) Bulk merge - will be merged all projects in the current directory
merger.exe bulk-merge [a] [pathToConfig]
where:
    a - merge without validation, or A - merge with validation, by default = a
    pathToConfig- path to the merge config (by default ""merge-config.json"").

3) Bulk switch all projects. All subfolders in SourcesFolder will be switched to targetSourceUrl\subfolderName.
merger.exe bulk-switch targetSourceUrl [pathToConfig]
where:
    targetSourceUrl - switch to source url.
    pathToConfig - path to the merge config (by default ""merge-config.json"").

4) Collect info to understood difficulty of the future merge.
merger.exe spy sourceUrl targetUrl revisionFrom
where:
    sourceUrl - repository to merge from.
    targetUrl - repository to merge to.
    revisionFrom - revison when source was create from target, or the beginning of the merge

5) Update cache - svn information is cached. And you need to update it from time to time.
merger.exe update-cache [pathToConfig]
    pathToConfig - path to the merge config (by default ""merge-config.json"").

Configuration file description:
   {
       ""CacheFolder"":""%temp%/merger"", //directory to store repository cache.
       ""MessageDescription"": ""#00000: Merge."", //commit message description. Also message will contains merged revisions and merged commit messages.
       ""SourcesFolder"": """", //root folder with source code, to merge to, without name of the project.
       ""MergeUrl"":"""", //repository url, to do merge from, without name of the project.
       ""Validation"":[""msbuild""], //array of the commands to do validation - just set of the command line operations to execute.
       ""BulkProjectsNames"":"""", //if not empty, than bulk operations will process only projects from this collection (They should be divided by ',').
       ""RevisionsToMerge"":"""", //if not empty, than will be  merged only specified revisions (They should be divided by ',').
       ""AuthorsToMerge"":"""", // if not empty, than will be merged only commits by specified authors (They should be divided by ',').
       ""MessagesToMerge"":"""", // if not empty, than will be merged only commits with messages, which contains at least one of the specified message (They should be divided by ',').
       ""OperationsBeforeMerge"":""CRU"", // list of the operations before merge: C-cleanup, R-revert, U-update. You can use any combination.
       ""MergeFrom"":"""", // if you need to merge changes from revision, specify value, otherwise merge will start on last folder copy.
       ""UseDryMerge"":""false"" //if true - no changes will be saved on disc, when merge have conflicts
    }
");
        }

        public void Dispose()
        {
            container.Dispose();
        }
    }
}